﻿using System.Threading;
using Microsoft.Xna.Framework;

namespace  RaptorEngine.Threads
{
	class DoubleBuffer
	{
		private ChangeBuffer[] buffers;

		private volatile int currentUpdateBuffer;
		private volatile int currentRenderBuffer;
		private volatile GameTime gameTime;

		private AutoResetEvent renderFrameStart;
		private AutoResetEvent renderFrameEnd;
		private AutoResetEvent updateFrameStart;
		private AutoResetEvent updateFrameEnd;

		public int ChangeMessageCount { get; set; }

		public DoubleBuffer(out ChangeBuffer updateBuffer)
		{
			//create the buffers
			buffers = new ChangeBuffer[2];
			buffers[0] = new ChangeBuffer();
			buffers[1] = new ChangeBuffer();

			//create the WaitHandlers
			renderFrameStart = new AutoResetEvent(false);
			renderFrameEnd = new AutoResetEvent(false);
			updateFrameStart = new AutoResetEvent(false);
			updateFrameEnd = new AutoResetEvent(false);

			//reset the values
			Reset();

			updateBuffer = buffers[currentUpdateBuffer];
		}

		public void Reset()
		{
			//reset the buffer indexes
			currentUpdateBuffer = 0;
			currentRenderBuffer = 1;

			//set all events to non-signaled
			renderFrameStart.Reset();
			renderFrameEnd.Reset();
			updateFrameStart.Reset();
			updateFrameEnd.Reset();
		}

		public void CleanUp()
		{
			//relese system resources
			renderFrameStart.Close();
			renderFrameEnd.Close();
			updateFrameStart.Close();
			updateFrameEnd.Close();
		}


        public ChangeBuffer StartUpdateProcessing(ChangeBuffer updateBuffer, out GameTime gameTime)
		{
			//wait for start signal
			updateFrameStart.WaitOne();
			Thread.MemoryBarrier();
			//get the update buffer
			updateBuffer = buffers[currentUpdateBuffer];
			//get the game time
			gameTime = this.gameTime;

		    return updateBuffer;
		}

		public void StartRenderProcessing(out ChangeBuffer renderBuffer, out GameTime gameTime)
		{
			//wait for start signal
			renderFrameStart.WaitOne();
			Thread.MemoryBarrier();
			//get the render buffer
			renderBuffer = buffers[currentRenderBuffer];
			//ret the game time
			gameTime = this.gameTime;
		}

		public void SubmitUpdate()
		{
			Thread.MemoryBarrier();
			//update is done
			updateFrameEnd.Set();
		}
		public void SubmitRender()
		{
			Thread.MemoryBarrier();
			//render is done
			renderFrameEnd.Set();
		}

		private void SwapBuffers()
		{
			currentRenderBuffer = currentUpdateBuffer;
			currentUpdateBuffer = (currentUpdateBuffer + 1) % 2;
			ChangeMessageCount = buffers[currentRenderBuffer].Messages.Count;
		}

		public void GlobalStartFrame(GameTime gameTime)
		{
			this.gameTime = gameTime;
			SwapBuffers();
			//RemoveAndAddScenes();
			RemoveAndAddEntities();

			GameEngine.Instance.Input.Update();
			//signal the render and update threads to start processing
			renderFrameStart.Set();
			updateFrameStart.Set();
		}

		private void RemoveAndAddEntities()
		{
			foreach (Scene scene in GameEngine.Instance.Scene.Scenes)
			{
				if (scene.IsVisible || scene.IsActive)
				{
					foreach (Entity entity in scene.ToBeRemoved)
					{
						scene.RemoveEntityWhenInSync(entity);
					}
					scene.ToBeRemoved.Clear();
					foreach (Entity entity in scene.ToBeAdded)
					{
						scene.Entities.Add(entity);
					}
					scene.ToBeAdded.Clear();
				}
			}
		}
	}
}
